/*******************************************************************************
 * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@gmail.com
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    emil.crumhorn@gmail.com - initial API and implementation
 *******************************************************************************/

package org.eclipse.nebula.widgets.calendarcombo;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.DragDetectListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.HelpListener;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;

/**
 * <b>CalendarCombo - SWT Widget - 2005-2008. Version 1.0. &copy; Emil Crumhorn
 * - emil dot crumhorn at gmail dot com.</b>
 * <p>
 * <b>Website</b><br>
 * If you want more info or more documentation, please visit: <a
 * href="http://www.hexapixel.com/">http://www.hexapixel.com</a>
 * <p>
 * <b>Description</b><br>
 * CalendarCombo is a widget that opens a calendar when dropped down. The
 * calendar is modelled after Microsoft Outlook's calendar widget and acts and
 * behaves exactly the same (and it is also theme based). The combo is not based
 * on CCombo (as many other custom implementations), but is instead attached to
 * the native Combo box.
 * <p>
 * <b>Example Creation Code</b><br>
 * <code>
 * CalendarCombo cCombo = new CalendarCombo(parentControl, SWT.READ_ONLY);<br>
 * ...<br>
 * </code>
 * <p>
 * <b>Another example using depending combos and date range selection on the
 * first combo</b><br>
 * <code>
 * CalendarCombo cComboStart = new CalendarCombo(parentControl, SWT.READ_ONLY, true);<br>
 * CalendarCombo cComboEnd = new CalendarCombo(parentControl, SWT.READ_ONLY);<br>
 * cComboStart.setDependingCombo(cComboEnd);<br>
 * </code> <br>
 * This will cause the end date for the date range selection to be populated in
 * the cComboEnd combo.
 * <p>
 * <b>Customizing</b><br>
 * There are two interfaces that are of importance for customizing, one is
 * IColorManager and the other is ISettings. Let's start with the IColorManager.
 * <p>
 * <b>IColorManager</b><br>
 * If you don't specify a color manager, the DefaultColorManager will be used.
 * The color manager's job is to return colors to the method that is painting
 * all the actual days and months etc. The colors that are returned from the
 * ColorManager will determine everything as far as looks go.
 * <p>
 * <b>ISettings</b><br>
 * To control the spacing between dates, various formats and text values, you
 * will want to implement the ISettings interface. If you don't specify one,
 * DefaultSettings will be used.
 * 
 * @author Emil Crumhorn
 * @version 1.1.2008.11.25
 */
public class CalendarCombo extends Composite {

	// the main combo box
	private Combo mCombo;
	private FlatCalendarCombo mFlatCombo;

	private Composite mComboControl;

	private int mComboStyle = SWT.NONE;

	// the shell holding the CalendarComposite
	private Shell mCalendarShell;

	private Listener mKillListener;

	private Listener mFilterListenerFocusIn;

	private Composite mParentComposite;

	// values for determining when the last view of the mCalendarShell was.
	// this is to determine how quick clicks should behave
	private long mLastShowRequest = 0;

	private long mLastKillRequest = 0;

	private CalendarCombo mDependingCombo;

	private CalendarComposite mCalendarComposite;

	private Calendar mStartDate;

	private Calendar mEndDate;

	private IColorManager mColorManager;

	private ISettings mSettings;

	private ArrayList<ICalendarListener> mListeners;

	private Calendar mDisallowBeforeDate;

	private Calendar mDisallowAfterDate;

	private boolean isReadOnly;

	private boolean isFlat;

	private int arrowButtonWidth;

	private boolean mAllowDateRange;

	private Calendar mCarbonPrePopupDate;

	private Listener mKeyDownListener;

	private boolean mParsingDate;

	private int mLastFireTime;

	private Calendar mLastNotificationDate;

	private Listener mOobClickListener;

	private List<IDateParseExceptionListener> mDateParseExceptionListeners;

	private Listener mOobDisplayFilterListener;

	protected static final boolean OS_CARBON = "carbon".equals(SWT.getPlatform());
	protected static final boolean OS_GTK = "gtk".equals(SWT.getPlatform());
	protected static final boolean OS_WINDOWS = "win32".equals(SWT.getPlatform());

	/*
	 * // Windows JNI Code for making a non-activated canvas, for reference if
	 * searching MSDN int extStyle = OS.GetWindowLong(canvas.handle,
	 * OS.GWL_EXSTYLE); extStyle = extStyle | 0x80000;
	 * OS.SetWindowLong(canvas.handle, OS.GWL_EXSTYLE, extStyle); 0x008000000 =
	 * WS_EX_NOACTIVATE;
	 */

	/**
	 * Creates a new calendar combo box with the given style.
	 * 
	 * @param parent
	 *            Parent control
	 * @param style
	 *            Combo style
	 */
	public CalendarCombo(Composite parent, int style) {
		super(parent, checkStyle(style));
		this.mComboStyle = style;
		this.mParentComposite = parent;
		init();
	}

	/**
	 * Creates a new calendar combo box with the given style.
	 * 
	 * @param parent
	 *            Parent control
	 * @param style
	 *            Combo style
	 * @param allowDateRange
	 *            Whether to allow date range selection (note that if there is
	 *            no depending CalendarCombo set you will have to deal with the
	 *            date range event yourself).
	 */
	public CalendarCombo(Composite parent, int style, boolean allowDateRange) {
		super(parent, checkStyle(style));
		this.mComboStyle = style;
		this.mParentComposite = parent;
		this.mAllowDateRange = allowDateRange;
		init();
	}

	/**
	 * Creates a new calendar mCombo box with the given style, ISettings and
	 * IColorManager implementations.
	 * 
	 * @param parent
	 *            Parent control
	 * @param style
	 *            Combo style
	 * @param settings
	 *            ISettings implementation
	 * @param colorManager
	 *            IColorManager implementation
	 */
	public CalendarCombo(Composite parent, int style, ISettings settings, IColorManager colorManager) {
		super(parent, checkStyle(style));
		this.mComboStyle = style;
		this.mParentComposite = parent;
		this.mSettings = settings;
		this.mColorManager = colorManager;
		init();
	}

	/**
	 * Creates a new calendar mCombo box with the given style, ISettings and
	 * IColorManager implementations.
	 * 
	 * @param parent
	 *            Parent control
	 * @param style
	 *            Combo style
	 * @param settings
	 *            ISettings implementation
	 * @param colorManager
	 *            IColorManager implementation
	 * @param allowDateRange
	 *            Whether to allow date range selection (note that if there is
	 *            no depending CalendarCombo set you will have to deal with the
	 *            date range event yourself).
	 */
	public CalendarCombo(Composite parent, int style, ISettings settings, IColorManager colorManager, boolean allowDateRange) {
		super(parent, checkStyle(style));
		this.mComboStyle = style;
		this.mParentComposite = parent;
		this.mSettings = settings;
		this.mColorManager = colorManager;
		this.mAllowDateRange = allowDateRange;
		init();
	}

	/**
	 * Lets you create a depending CalendarCombo box, which, when no dates are
	 * set on the current one will set the starting date when popped up to be
	 * the date of the pullDateFrom CalendarCombo, should that box have a date
	 * set in it.
	 * 
	 * @param parent
	 * @param style
	 * @param dependingCombo
	 *            CalendarCombo from where start date is pulled when no date has
	 *            been set locally.
	 */
	public CalendarCombo(Composite parent, int style, CalendarCombo dependingCombo) {
		super(parent, checkStyle(style));
		this.mParentComposite = parent;
		this.mDependingCombo = dependingCombo;
		this.mComboStyle = style;
		init();
	}

	/**
	 * Lets you create a depending CalendarCombo box, which, when no dates are
	 * set on the current one will set the starting date when popped up to be
	 * the date of the pullDateFrom CalendarCombo, should that box have a date
	 * set in it. When the depending combo is set and the allowDateRange flag is
	 * true, the depending combo will be the recipient of the end date of any
	 * date range selection.
	 * 
	 * @param parent
	 * @param style
	 * @param dependingCombo
	 *            CalendarCombo from where start date is pulled when no date has
	 *            been set locally.
	 * @param allowDateRange
	 *            Whether to allow date range selection
	 */
	public CalendarCombo(Composite parent, int style, CalendarCombo dependingCombo, boolean allowDateRange) {
		super(parent, checkStyle(style));
		this.mParentComposite = parent;
		this.mDependingCombo = dependingCombo;
		this.mComboStyle = style;
		this.mAllowDateRange = allowDateRange;
		init();
	}

	/**
	 * Lets you create a depending CalendarCombo box, which, when no dates are
	 * set on the current one will set the starting date when popped up to be
	 * the date of the pullDateFrom CalendarCombo, should that box have a date
	 * set in it.
	 * 
	 * @param parent
	 * @param style
	 * @param dependingCombo
	 *            CalendarCombo from where start date is pulled when no date has
	 *            been set locally.
	 * @param settings
	 *            ISettings implementation
	 * @param colorManager
	 *            IColorManager implementation
	 */
	public CalendarCombo(Composite parent, int style, CalendarCombo dependingCombo, ISettings settings, IColorManager colorManager) {
		super(parent, checkStyle(style));
		this.mParentComposite = parent;
		this.mDependingCombo = dependingCombo;
		this.mComboStyle = style;
		this.mSettings = settings;
		this.mColorManager = colorManager;
		init();
	}

	/**
	 * Lets you create a depending CalendarCombo box, which, when no dates are
	 * set on the current one will set the starting date when popped up to be
	 * the date of the pullDateFrom CalendarCombo, should that box have a date
	 * set in it. When the depending combo is set and the allowDateRange flag is
	 * true, the depending combo will be the recipient of the end date of any
	 * date range selection.
	 * 
	 * @param parent
	 * @param style
	 * @param dependingCombo
	 *            CalendarCombo from where start date is pulled when no date has
	 *            been set locally.
	 * @param settings
	 *            ISettings implementation
	 * @param colorManager
	 *            IColorManager implementation
	 * @param allowDateRange
	 *            Whether to allow date range selection
	 */
	public CalendarCombo(Composite parent, int style, CalendarCombo dependingCombo, ISettings settings, IColorManager colorManager,
			boolean allowDateRange) {
		super(parent, checkStyle(style));
		this.mParentComposite = parent;
		this.mDependingCombo = dependingCombo;
		this.mComboStyle = style;
		this.mSettings = settings;
		this.mColorManager = colorManager;
		init();
	}

	// remove styles we don't allow
	private static int checkStyle(int style) {
		int mask = SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.MULTI | SWT.NO_FOCUS | SWT.CHECK | SWT.VIRTUAL | SWT.FLAT;

		int newStyle = style & mask;
		return newStyle;
	}

	// lay out everything and add all our listeners
	private void init() {
		isReadOnly = ((mComboStyle & SWT.READ_ONLY) != 0);
		isFlat = ((mComboStyle & SWT.FLAT) != 0);

		mListeners = new ArrayList<ICalendarListener>();
		mDateParseExceptionListeners = new ArrayList<IDateParseExceptionListener>();

		// if click happens on a control that is not us, we kill
		mOobClickListener = new Listener() {
			public void handleEvent(Event event) {
				if (!isCalendarVisible())
					return;

				Control cc = Display.getDefault().getCursorControl();

				if (cc != mCalendarComposite) {
					if (cc != mCalendarShell) {
						boolean killIt = false;
						if (isFlat) {
							if (cc != mFlatCombo.getTextControl() && cc != mFlatCombo.getArrowButton()) {
								killIt = true;
							}
						} else {
							if (cc != mCombo) {
								killIt = true;
							}
						}

						if (killIt) {
							kill(44);
						}
					}
				}

			}
		};

		if (mColorManager == null)
			mColorManager = new DefaultColorManager();

		if (mSettings == null)
			mSettings = new DefaultSettings();

		arrowButtonWidth = mSettings.getWindowsButtonWidth();
		if (OS_CARBON)
			arrowButtonWidth = mSettings.getCarbonButtonWidth();
		else if (OS_GTK)
			arrowButtonWidth = mSettings.getGTKButtonWidth();

		GridLayout gl = new GridLayout();
		gl.horizontalSpacing = 0;
		gl.verticalSpacing = 0;
		gl.marginWidth = 0;
		gl.marginHeight = 0;
		setLayout(gl);

		if (isFlat) {
			mFlatCombo = new FlatCalendarCombo(this, this, mComboStyle | SWT.FLAT);
			mFlatCombo.setVisibleItemCount(0);
			mFlatCombo.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
			mComboControl = mFlatCombo;
		} else {
			mCombo = new Combo(this, mComboStyle);
			mCombo.setVisibleItemCount(0);
			mCombo.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
			mComboControl = mCombo;
		}

		// when a user types in a date we parse it when they traverse away in
		// any sense or form (focus lost etc)
		if (!isReadOnly) {
			Listener traverseListener = new Listener() {
				public void handleEvent(Event event) {
					// double event, ignore
					if (event.time == mLastFireTime) {
						return;
					}

					if (event.detail == 16 || event.detail == 8 || event.detail == 4 || event.detail == 0) {
						parseTextDate();
					}

					mLastFireTime = event.time;
				}
			};

			// deal with traverse-away/in/return events to parse dates
			mComboControl.addListener(SWT.Traverse, traverseListener);
			mComboControl.addListener(SWT.FocusOut, traverseListener);

			mKeyDownListener = new Listener() {
				public void handleEvent(Event event) {
					// if event didn't happen on this combo, ignore it
					if (isFlat) {
						if (event.widget != mFlatCombo.getTextControl())
							return;
					} else {
						if (event.widget != mCombo) {
							return;
						}
					}

					if (mSettings.keyboardNavigatesCalendar()) {

						if (event.keyCode == SWT.ARROW_DOWN) {
							Control ctrl = (isFlat ? (Control) mFlatCombo.getTextControl() : mCombo);

							if (Display.getDefault().getFocusControl() == ctrl) {
								if (!isCalendarVisible())
									showCalendar();
								else {
									mCalendarComposite.keyPressed(event.keyCode, event.stateMask);
									event.doit = false;
								}
							}
						} else {
							if (isCalendarVisible()) {
								mCalendarComposite.keyPressed(event.keyCode, event.stateMask);
								// eat event or cursor will jump around in combo
								// as well
								event.doit = false;
							}
						}
					} else {
						boolean acceptedEvent = (event.keyCode == SWT.ARROW_DOWN || event.keyCode == SWT.ARROW_UP);
						if (OS_CARBON) {
							acceptedEvent = (event.character == mSettings.getCarbonArrowDownChar() || event.character == mSettings
									.getCarbonArrowUpChar());
						}

						if (acceptedEvent) {
							boolean up = event.keyCode == SWT.ARROW_UP;
							if (OS_CARBON)
								up = event.character == mSettings.getCarbonArrowUpChar();

							int cursorLoc = isFlat ? mFlatCombo.getSelection().x : mCombo.getSelection().x;
							// first, parse the date, we don't care if it's some
							// fantastic format we can parse, parse it again
							parseTextDate();
							// once it's parsed, set it to the default format,
							// that way we KNOW where certain parts of the date
							// are
							if (mStartDate != null) {
								setComboText(DateHelper.getDate(mStartDate, mSettings.getDateFormat()));

								String df = mSettings.getDateFormat();

								event.doit = false;
								if (isFlat)
									mFlatCombo.setSelection(new Point(cursorLoc, cursorLoc));
								else
									mCombo.setSelection(new Point(cursorLoc, cursorLoc));

								// split the date format. we do this as a date
								// format of M/d/yyyy for example can still have
								// 2 digits as M or d .
								String separatorChar = null;
								char[] accepted = mSettings.getAcceptedDateSeparatorChars();
								for (int i = 0; i < accepted.length; i++) {
									if (df.indexOf(String.valueOf(accepted[i])) > -1) {
										separatorChar = String.valueOf(accepted[i]);
									}
								}

								int sectionStart = 0;
								int sectionEnd = 0;

								int splitCount = 0;

								// get the format
								String oneChar = "";
								if (separatorChar != null) {
									// now find how many separator chars we are
									// from the left side of the date in the box
									// to where the cursor is, that will tell us
									// what part of
									// the date format we're on
									String comboText = isFlat ? mFlatCombo.getText() : mCombo.getText();

									for (int i = 0; i < comboText.length(); i++) {
										if (i >= cursorLoc)
											break;

										if (comboText.charAt(i) == separatorChar.charAt(0))
											splitCount++;
									}

									StringTokenizer st = new StringTokenizer(df, separatorChar);
									int count = 0;
									while (st.hasMoreTokens()) {
										String tok = st.nextToken();
										if (count == splitCount) {
											oneChar = tok;
											break;
										}
										count++;
									}
								} else {
									oneChar = mSettings.getDateFormat().substring(cursorLoc, cursorLoc + 1);

									// get the whole part, bit tricky I suppose,
									// but
									// we fetch everything that matches the
									// format
									// at the position we're at, easy enough
									StringBuffer buf = new StringBuffer();
									int start = cursorLoc;

									while (start >= 0) {
										if (mSettings.getDateFormat().charAt(start) == oneChar.charAt(0)) {
											buf.append(mSettings.getDateFormat().charAt(start));
										} else {
											break;
										}
										start--;
									}
									start = cursorLoc + 1;
									while (start < mSettings.getDateFormat().length()) {
										if (mSettings.getDateFormat().charAt(start) == oneChar.charAt(0)) {
											buf.append(mSettings.getDateFormat().charAt(start));
										} else {
											break;
										}
										start++;
									}

									sectionStart = mSettings.getDateFormat().indexOf(buf.toString());
									sectionEnd = sectionStart + buf.toString().length();
								}

								// Korean dates and some others have spaces in
								// them (!?)
								oneChar = oneChar.replaceAll(" ", "");
								if (oneChar.length() == 0)
									return;

								// now we now what to increase/decrease, lets do
								// it
								int calType = DateHelper.getCalendarTypeForString(oneChar);

								if (calType != -1) {
									mStartDate.add(calType, up ? 1 : -1);

									String newDate = DateHelper.getDate(mStartDate, mSettings.getDateFormat());

									setComboText(newDate);

									if (separatorChar != null) {
										// we need to update the selection after
										// we've set the date
										// figure out cursor location, now we
										// have to use the date in the box again
										StringTokenizer st = new StringTokenizer(newDate, separatorChar);
										int count = 0;
										boolean stop = false;
										while (st.hasMoreTokens()) {
											String tok = st.nextToken();

											// we found our section
											if (count == splitCount) {
												sectionEnd = sectionStart + tok.length();
												stop = true;
												break;
											}

											// if we're stopping, break out
											if (stop)
												break;

											// add on separator chars for each
											// loop iteration post 0
											sectionStart += 1;
											// and token length
											sectionStart += tok.length();

											count++;
										}
									}

									// set the selection for us
									if (isFlat) {
										mFlatCombo.setSelection(new Point(sectionStart, sectionEnd));
									} else {
										mCombo.setSelection(new Point(sectionStart, sectionEnd));

									}

								}
							}
						}
					}
				}
			};

			Display.getDefault().addFilter(SWT.KeyDown, mKeyDownListener);
		}

		if (isFlat) {
			mComboControl.addListener(SWT.FocusOut, new Listener() {
				public void handleEvent(Event event) {
					kill(98);
				}
			});
		}

		mComboControl.addListener(SWT.MouseDown, new Listener() {
			public void handleEvent(Event event) {
				// click in the text area? ignore
				if (!isFlat) {
					if (isTextAreaClick(event)) {
						if (isCalendarVisible()) {
							kill(16);
						}

						return;
					}
				}

				// kill calendar if visible and do nothing else
				if (isCalendarVisible()) {
					kill(15);
					return;
				}
				// a very small time between the close and the open means it was
				// a click on the
				// arrow down to close, and we don't open it again in that case.
				// The next click will open.
				// this is the expected behavior.
				mLastShowRequest = Calendar.getInstance(mSettings.getLocale()).getTimeInMillis();

				long diff = mLastKillRequest - mLastShowRequest;
				if (diff > -100 && diff < 0) {
					return;
				}

				showCalendar();
			}
		});

		mKillListener = new Listener() {
			public void handleEvent(Event event) {
				if (event.keyCode == SWT.ESC) {
					kill(77);
					return;
				}

				// ignore arrow down events for killing popup
				if (event.keyCode == SWT.ARROW_DOWN || event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_LEFT
						|| event.keyCode == SWT.ARROW_RIGHT || event.keyCode == SWT.CR || event.keyCode == SWT.LF) {
					return;
				}

				kill(1);
			}
		};

		int[] comboEvents = { SWT.Dispose, SWT.Move, SWT.Resize };
		for (int i = 0; i < comboEvents.length; i++) {
			this.addListener(comboEvents[i], mKillListener);
		}

		int[] arrowEvents = {
		// SWT.Selection
		};
		for (int i = 0; i < arrowEvents.length; i++) {
			mComboControl.addListener(arrowEvents[i], mKillListener);
		}

		mFilterListenerFocusIn = new Listener() {
			public void handleEvent(Event event) {
				if (OS_CARBON) {
					Widget widget = event.widget;
					if (widget instanceof CalendarComposite == false)
						kill(2);

					// on mac, select all text in combo if we are the control
					// that gained focus
					if (mComboControl == Display.getDefault().getFocusControl()) {
						if (isFlat) {
							mFlatCombo.getTextControl().selectAll();
						} else {
							mCombo.setSelection(new Point(0, mCombo.getText().length()));
						}
					}
				} else {
					long now = Calendar.getInstance(mSettings.getLocale()).getTimeInMillis();
					long diff = now - mLastShowRequest;
					if (diff > 0 && diff < 100)
						return;

					if (!isCalendarVisible())
						return;

					// don't force focus, user clicked another control, let it
					// grab the focus or it'll be odd behavior
					if (!isFlat)
						kill(3, true);
				}
			}
		};

		Shell parentShell = mParentComposite.getShell();

		if (parentShell != null) {
			parentShell.addControlListener(new ControlListener() {
				public void controlMoved(ControlEvent e) {
					kill(4);
				}

				public void controlResized(ControlEvent e) {
					kill(5);
				}
			});

			parentShell.addListener(SWT.Deactivate, new Listener() {
				public void handleEvent(Event event) {
					Point mouseLoc = Display.getDefault().getCursorLocation();

					// with no focus shells, buttons will steal focus, and cause
					// deactivate events when clicked
					// so if the deactivate came from a mouse being over any of
					// our buttons, that is the same as
					// if we clicked them.
					if (mCalendarComposite != null && mCalendarComposite.isDisposed() == false)
						mCalendarComposite.externalClick(mouseLoc);

					if (!isFlat)
						kill(6);
				}
			});

			parentShell.addFocusListener(new FocusListener() {
				public void focusGained(FocusEvent e) {

				}

				public void focusLost(FocusEvent e) {
					kill(7);
				}
			});
		}

		mOobDisplayFilterListener = new Listener() {
			public void handleEvent(Event event) {
				// This may seem odd, but if the event is the CalendarCombo, we
				// actually clicked outside the widget and the CalendarComposite
				// area, meaning -
				// we clicked outside our widget, so we close it.
				if (event.widget instanceof CalendarCombo) {
					kill(8);
					mComboControl.setFocus();
				}
			}
		};

		Display.getDefault().addFilter(SWT.MouseDown, mOobDisplayFilterListener);

		// remove listener when mCombo is disposed
		Display.getDefault().addFilter(SWT.FocusIn, mFilterListenerFocusIn);
		mComboControl.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent event) {
				Display.getDefault().removeFilter(SWT.FocusIn, mFilterListenerFocusIn);
				Display.getDefault().removeFilter(SWT.MouseDown, mOobDisplayFilterListener);
				if (mKeyDownListener != null)
					Display.getDefault().removeFilter(SWT.KeyDown, mKeyDownListener);

				if (mSettings.getCarbonDrawFont() != null)
					mSettings.getCarbonDrawFont().dispose();

				if (mSettings.getWindowsMonthPopupDrawFont() != null)
					mSettings.getWindowsMonthPopupDrawFont().dispose();
			}
		});

		// mac's editable combos behave differently
		if (OS_CARBON) {
			// this code seems obsolete as of June 24th 2008, Allow text input
			// on Mac, we parse it anyways now, which we didn't do prior
			// leaving code in for a while to remind myself
			/*
			 * mCombo.addVerifyListener(new VerifyListener() { public void
			 * verifyText(VerifyEvent event) { if (isCalendarVisible() ||
			 * mAllowTextEntry) { ; } else { event.doit = false; } } });
			 */

			// this is the most messed up thing ever, but it works. Basically,
			// OSX will pop up a combo of 1 item to let you pick it
			// even when it's blantantly obvious that you can only pick that one
			// item.. duh.. Anyway, the Paint event actually fires PRIOR to the
			// combo
			// opening, and our "cure" to the popup issue is to quickly remove
			// the item from the combo before it does open, thus, it doesn't
			// show the selector list,
			// but our popup instead. The side effect is that the combo appears
			// blank while the calendar is open. But as we reset the date when
			// no selection has been made, it's all good! It ain't pretty, but
			// it does the job
			if (isReadOnly) {
				if (isFlat) {
					mFlatCombo.addListener(SWT.Paint, new Listener() {
						public void handleEvent(Event event) {
							mCarbonPrePopupDate = getDate();
							// mFlatCombo.removeAll();
						}
					});
				} else {
					mCombo.addListener(SWT.Paint, new Listener() {
						public void handleEvent(Event event) {
							mCarbonPrePopupDate = getDate();
							mCombo.removeAll();

						}
					});
				}
			}
		}

		parentShell.addShellListener(new ShellListener() {
			public void shellActivated(ShellEvent event) {
			}

			public void shellClosed(ShellEvent event) {
				// shell killed
				kill(9);
			}

			public void shellDeactivated(ShellEvent event) {
			}

			public void shellDeiconified(ShellEvent event) {
			}

			public void shellIconified(ShellEvent event) {
				// shell no longer in focus (like.. WindowsKey + M on WinXP or
				// such)
				kill(10);
			}
		});
	}

	// parses the date, and really tries
	private void parseTextDate() {
		// listeners by users can cause infinite loops as we notify, they set
		// dates on
		// notify, etc, so don't allow parsing if we haven't finished internally
		// yet.
		if (mParsingDate) {
			return;
		}

		try {
			mParsingDate = true;

			String comboText = (isFlat ? mFlatCombo.getText() : mCombo.getText());

			if (comboText.length() == 0 && mStartDate != null) {
				mStartDate = null;
				setText("");
				if (mLastNotificationDate != null) {
					notifyDateChangedToNull();
				}
				return;
			}

			mStartDate = DateHelper.parse(comboText, mSettings.getLocale(), mSettings.getDateFormat(),
					mSettings.getAcceptedDateSeparatorChars(), mSettings.getAdditionalDateFormats());
			updateDate();
			notifyDateChanged();
			mParsingDate = false;
		} catch (CalendarDateParseException dpe) {
			if (!mDateParseExceptionListeners.isEmpty()) {
				notifyDateParseException(dpe);
			} else {
				dpe.printStackTrace();
			}
		} catch (Exception err) {
			err.printStackTrace();
		} finally {
			mParsingDate = false;
		}
	}

	private void notifyDateParseException(CalendarDateParseException dpe) {
		for (int i = 0; i < mDateParseExceptionListeners.size(); i++) {
			((IDateParseExceptionListener) mDateParseExceptionListeners.get(i)).parseExceptionThrown(dpe);
		}
	}

	private void notifyDateChangedToNull() {
		if (mLastNotificationDate == null) {
			return;
		}

		for (int i = 0; i < mListeners.size(); i++) {
			try {
				((ICalendarListener) mListeners.get(i)).dateChanged(mStartDate);
			} catch (Exception err) {
				err.printStackTrace();
			}
		}

		mLastNotificationDate = null;
	}

	private void notifyDateRangeChanged() {
		for (int i = 0; i < mListeners.size(); i++) {
			try {
				((ICalendarListener) mListeners.get(i)).dateRangeChanged(mStartDate, mEndDate);
			} catch (Exception err) {
				err.printStackTrace();
			}
		}
	}

	private void notifyDateChanged() {
		if (mStartDate == null) {
			notifyDateChangedToNull();
			return;
		}

		if (mLastNotificationDate != null && mStartDate != null) {
			if (DateHelper.sameDate(mLastNotificationDate, mStartDate)) {
				return;
			}
		}

		mLastNotificationDate = (Calendar) mStartDate.clone();

		for (int i = 0; i < mListeners.size(); i++) {
			try {
				((ICalendarListener) mListeners.get(i)).dateChanged(mStartDate);
			} catch (Exception err) {
				err.printStackTrace();
			}
		}
	}

	// checks whether a click was actually in the text area of a combo and not
	// on the arrow button. This is a hack by all means,
	// as there is (currently) no way to get the actual button from a combo.
	private boolean isTextAreaClick(Event event) {
		// read-only combos open on click anywhere
		if (isReadOnly)
			return false;

		Point size = isFlat ? mFlatCombo.getSize() : mCombo.getSize();
		Rectangle rect = null;

		rect = new Rectangle(0, 0, size.x - arrowButtonWidth, size.y);
		if (isInside(event.x, event.y, rect))
			return true;

		return false;
	}

	// check whether a pixel value is inside a rectangle
	private boolean isInside(int x, int y, Rectangle rect) {
		if (rect == null)
			return false;

		return x >= rect.x && y >= rect.y && x <= (rect.x + rect.width) && y <= (rect.y + rect.height);
	}

	/**
	 * Sets the current date. Date will be automatically displayed in the text
	 * area of the combo according to the defined date format (in settings).
	 * 
	 * @param date
	 *            Date to set
	 */
	public synchronized void setDate(Date date) {
		checkWidget();
		if (date == null) {
			clear();
		} else {
			Calendar cal = Calendar.getInstance(mSettings.getLocale());
			cal.setTime(date);
			setDate(cal);
		}
	}

	/**
	 * Sets the current date. Date will be automatically displayed in the text
	 * area of the combo according to the defined date format (in settings).
	 * 
	 * @param cal
	 *            Calendar to set
	 */
	public synchronized void setDate(Calendar cal) {
		checkWidget();
		mStartDate = cal;
		updateDate();
	}

	/**
	 * Sets the text in the combo area to the given value. Do note that if you
	 * set a text that is a non-pareseable date string according to the
	 * currently set date format, that string will be replaced or removed when
	 * the user opens the popup. This is mainly for disabled combos or combos
	 * where you need to add additional control to what's displayed in the text
	 * area.
	 * 
	 * @param text
	 *            Text to display
	 */
	public synchronized void setText(final String text) {
		checkWidget();

		String txt = isFlat ? mFlatCombo.getText() : mCombo.getText();

		if (txt.equals(text))
			return;

		setComboText(text);
	}

	/**
	 * 
	 * @param text
	 */
	private void setComboText(String text) {

		if (isFlat) {
			// mFlatCombo.removeAll();
			mFlatCombo.setText(text);
			// mFlatCombo.select(0);
		} else {
			mCombo.removeAll();
			mCombo.add(text);
			mCombo.select(0);
		}
	}

	/**
	 * 
	 * @param debug
	 */
	private synchronized void kill(int debug) {
		kill(debug, false);
	}

	/**
	 * kills the popup area and unhooks various listeners, takes an integer so
	 * that we can debug where the close comes from easier
	 */
	private synchronized void kill(int debug, boolean skipFocus) {
		if (mCalendarComposite == null)
			return;
		if (mCalendarComposite.isDisposed())
			return;

		if (mCalendarComposite != null && mCalendarComposite.isMonthPopupActive())
			return;

		// System.err.println(debug);

		if (mCalendarShell != null && !mCalendarShell.isDisposed()) {
			mLastKillRequest = Calendar.getInstance(mSettings.getLocale()).getTimeInMillis();
			mCalendarShell.setVisible(false);
			mCalendarShell.dispose();
		}

		Display.getDefault().removeFilter(SWT.KeyDown, mKillListener);
		Display.getDefault().removeFilter(SWT.MouseDown, mOobClickListener);
		Display.getDefault().removeFilter(SWT.MouseDown, mOobDisplayFilterListener);

		if (mComboControl != null && !mComboControl.isDisposed()) {
			mComboControl.setCapture(false);
			// have to traverse escape key after the fake popup is closed or the
			// "0 length" popup will have focus
			// traversing forces it to close
			mComboControl.traverse(SWT.TRAVERSE_ESCAPE);
			// if (getDate().length() != 0)
			// setText(DateHelper.getFormattedDate(DateHelper.getDate(getDate(),
			// mDateFormat), mDateFormat));
		}

		if (OS_CARBON) {
			if (mCarbonPrePopupDate != null)
				setDate(mCarbonPrePopupDate);
		}

		if (!skipFocus) {
			if (isFlat) {
				mFlatCombo.getTextControl().setFocus();
			} else {
				mCombo.setFocus();
			}
		}
	}

	private boolean isCalendarVisible() {
		/*
		 * try { throw new Exception(); } catch (Exception err) {
		 * err.printStackTrace(); }
		 */return (mCalendarShell != null && !mCalendarShell.isDisposed());
	}

	/**
	 * Pops open the calendar popup.
	 */
	public void openCalendar() {
		checkWidget();
		if (isCalendarVisible()) {
			return;
		}

		showCalendar();
	}

	/**
	 * Closes the calendar popup.
	 */
	public void closeCalendar() {
		checkWidget();
		if (!isCalendarVisible()) {
			return;
		}

		kill(99);
	}

	/**
	 * Returns the set date as the raw String representation that is currently
	 * displayed in the combo.
	 * 
	 * @return String date
	 */
	public String getDateAsString() {
		checkWidget();
		String text = isFlat ? mFlatCombo.getText() : mCombo.getText();
		if (text.equals(""))
			return "";

		return text;
	}

	/**
	 * Returns the currently set date.
	 * 
	 * @return Calendar of date selection or null.
	 */
	public Calendar getDate() {
		checkWidget();
		if (!isReadOnly)
			parseTextDate();

		return mStartDate;
	}

	/*
	 * private void setDateBasedOnComboText() { try { Date d =
	 * DateHelper.getDate(isFlat ? mFlatCombo.getText() : mCombo.getText(),
	 * mSettings.getDateFormat()); setDate(d); } catch (Exception err) { // we
	 * don't care } }
	 */

	// shows the calendar area
	public synchronized void showCalendar() {
		try {
			// this is part of the OSX issue where the popup for a combo is
			// shown even if there is only 1 item to select (dumb).
			// as we remove the date just prior to the popup opening, here, we
			// add it again, so it doesn't look like anything happened to the
			// user
			// when in fact we removed and added something in the blink of an
			// eye, just so that the combo would not open its own popup. This
			// seems to work
			// without a hitch -- fix: June 21, 2008
			if (OS_CARBON && isReadOnly && mCarbonPrePopupDate != null)
				setDate(mCarbonPrePopupDate);

			// bug fix: Apr 18, 2008 - if we do various operations prior to
			// actually fetching any newly entered text into the
			// (non-read only) combo, we'll lose that edit, so fetch the combo
			// text now so that we can check it later down in the code
			// reported by: B. Haje
			parseTextDate();

			String comboText = isFlat ? mFlatCombo.getText() : mCombo.getText();

			mComboControl.setCapture(true);
			// some weird bug with opening, selecting, closing, then opening
			// again, which blanks out the mCombo the first time around..
			if (!isFlat)
				mCombo.select(0);

			// kill any old
			if (isCalendarVisible()) {
				// bug fix part #2, apr 23. Repeated klicking open/close will
				// get here, so we need to do the same bug fix as before but
				// somewhat differently
				// as we need to update the date object as well. This is
				// basically only for non-read-only combos, but the fix is
				// universally applicable.
				setComboText(comboText);
				parseTextDate();
				kill(11);
				return;
			}

			mComboControl.setFocus();

			// bug fix: Apr 18, 2008 (see above)
			// the (necessary) setFocus call above for some reason screws up any
			// text entered by the user, so we actually have to set the text
			// back onto the combo, despite the fact we pulled the data just a
			// few lines ago.
			setComboText(comboText);

			Display.getDefault().addFilter(SWT.KeyDown, mKillListener);
			Display.getDefault().addFilter(SWT.MouseDown, mOobClickListener);

			mCalendarShell = new Shell(Display.getDefault().getActiveShell(), SWT.ON_TOP | SWT.NO_TRIM | SWT.NO_FOCUS);
			mCalendarShell.setLayout(new FillLayout());
			if (OS_CARBON)
				mCalendarShell.setSize(mSettings.getCalendarWidthMacintosh(), mSettings.getCalendarHeightMacintosh());
			else
				mCalendarShell.setSize(mSettings.getCalendarWidth(), mSettings.getCalendarHeight());

			mCalendarShell.addShellListener(new ShellListener() {
				public void shellActivated(ShellEvent event) {
				}

				public void shellClosed(ShellEvent event) {
					// shell killed
					kill(12);
				}

				public void shellDeactivated(ShellEvent event) {
				}

				public void shellDeiconified(ShellEvent event) {
				}

				public void shellIconified(ShellEvent event) {
					// shell no longer in focus (like.. WindowsKey + M on WinXP
					// or such)
					kill(13);
				}
			});

			// if no date has been set, and we have another calendar widget to
			// pull from, grab that date.
			// if we do have a date set, load it as an object we can actually
			// use.
			Calendar pre = null;

			if (comboText != null && comboText.length() > 1) {
				try {
					Date dat = DateHelper.getDate(comboText, mSettings.getDateFormat());
					if (dat != null) {
						pre = Calendar.getInstance(mSettings.getLocale());
						pre.setTime(dat);
					}
				} catch (Exception err) {
					List<?> otherFormats = mSettings.getAdditionalDateFormats();
					if (otherFormats != null) {
						boolean dateSet = false;
						for (int i = 0; i < otherFormats.size(); i++) {
							if (!dateSet) {
								String format = (String) otherFormats.get(i);
								try {
									Date date = DateHelper.getDate(comboText, format);
									setDate(date);
									Calendar cal = Calendar.getInstance(mSettings.getLocale());
									cal.setTime(date);
									pre = cal;
									dateSet = true;
								} catch (Exception e) {
									// Don't care about the exception
								}
							}
						}

						if (!dateSet) {
							// non parseable date, set the last used date if
							// any, otherwise set nodateset text
							if (mStartDate != null)
								setDate(mStartDate);
							else {
								setComboText(mSettings.getNoDateSetText());
							}
						}
					} else {
						// unparseable date, set the last used date if any,
						// otherwise set nodateset text
						if (mStartDate != null)
							setDate(mStartDate);
						else {
							setComboText(mSettings.getNoDateSetText());
						}
					}
				}

			} else {
				if (mDependingCombo != null) {
					// we need to pull the date from the depending combo's text
					// area as it may be non-read-only, so we can't rely on the
					// date
					Calendar date = null;
					try {
						Date d = DateHelper.getDate(mDependingCombo.getCombo().getText(), mSettings.getDateFormat());
						date = Calendar.getInstance(mSettings.getLocale());
						date.setTime(d);
					} catch (Exception err) {
						date = mDependingCombo.getDate();
					}

					if (date != null) {
						pre = Calendar.getInstance(mSettings.getLocale());
						pre.setTime(date.getTime());
					}
				}
			}

			// create the calendar composite
			mCalendarComposite = new CalendarComposite(mCalendarShell, pre, mDisallowBeforeDate, mDisallowAfterDate, mColorManager,
					mSettings, mAllowDateRange, mStartDate, mEndDate);
			/*
			 * mCalendarComposite.addCalendarListener(new ICalendarListener() {
			 * 
			 * public void dateChanged(Calendar date) { mStartDate = date;
			 * notifyDateChanged(); }
			 * 
			 * public void dateRangeChanged(Calendar start, Calendar end) {
			 * mStartDate = start; mEndDate = end; notifyDateChanged(); }
			 * 
			 * public void popupClosed() { }
			 * 
			 * });
			 */
			mCalendarComposite.addMainCalendarListener(new ICalendarListener() {
				public void dateChanged(Calendar date) {
					if (!isFlat)
						mCombo.removeAll();

					if (OS_CARBON)
						mCarbonPrePopupDate = date;

					mStartDate = date;
					if (date == null) {
						setText("");
					} else {
						updateDate();
					}

					if (mStartDate == null) {
						notifyDateChangedToNull();
					} else {
						notifyDateChanged();
					}
				}

				public void dateRangeChanged(Calendar start, Calendar end) {
					if (!isFlat)
						mCombo.removeAll();

					mStartDate = start;

					if (OS_CARBON)
						mCarbonPrePopupDate = start;

					mEndDate = end;
					if (start == null) {
						setText("");
					} else {
						updateDate();
					}

					if (mDependingCombo != null) {
						if (end != null) {
							mDependingCombo.setDate(end);
						} else {
							mDependingCombo.setText("");
						}

						notifyDateRangeChanged();
					} else {
						notifyDateChanged();
					}

				}

				public void popupClosed() {
					kill(14);
					for (int i = 0; i < mListeners.size(); i++) {
						((ICalendarListener) mListeners.get(i)).popupClosed();
					}
				}
			});

			// figure out where to put the calendar composite shell
			Point calLoc = mComboControl.getLocation();
			Point size = mComboControl.getSize();

			Point loc = null;
			if (mSettings.showCalendarInRightCorner()) {
				loc = new Point(calLoc.x + size.x - mCalendarShell.getSize().x, calLoc.y + size.y);
			} else {
				loc = new Point(calLoc.x, calLoc.y + size.y);
			}
			loc = toDisplay(loc);
			// don't let it slip out on the left side of the screen
			if (loc.x < 0) {
				loc.x = 0;
			}

			// If the mCalendarShell is going to disappear of the bottom of the
			// screen then push the dialog upwards instead of downwards.
			Rectangle bounds = Display.getDefault().getBounds();
			if (loc.y + mCalendarShell.getSize().y > bounds.height) {
				loc.y = loc.y - mCalendarShell.getSize().y - size.y;
			}

			mCalendarShell.setLocation(loc);
			mCalendarShell.setVisible(true);

		} catch (Exception e) {
			mComboControl.setCapture(false);
			e.printStackTrace();
			// don't really care
		}
	}

	/**
	 * Adds a {@link IDateParseExceptionListener} that listens to date parse
	 * exceptions
	 * 
	 * @param listener
	 *            to add
	 */
	public void addDateParseExceptionListener(IDateParseExceptionListener listener) {
		checkWidget();
		if (!mDateParseExceptionListeners.contains(listener)) {
			mDateParseExceptionListeners.add(listener);
		}
	}

	/**
	 * Removes a {@link IDateParseExceptionListener} listener.
	 * 
	 * @param listener
	 *            to remove
	 */
	public void removeDateParseExceptionListener(IDateParseExceptionListener listener) {
		checkWidget();
		mDateParseExceptionListeners.remove(listener);
	}

	/**
	 * Adds a calendar listener.
	 * 
	 * @param listener
	 *            Listener
	 */
	public void addCalendarListener(ICalendarListener listener) {
		checkWidget();
		if (!mListeners.contains(listener)) {
			mListeners.add(listener);
		}
	}

	/**
	 * Removes a calendar listener.
	 * 
	 * @param listener
	 *            Listener
	 */
	public void removeCalendarListener(ICalendarListener listener) {
		checkWidget();
		mListeners.remove(listener);
	}

	/**
	 * Returns the combo box widget.
	 * <p>
	 * <font color="red"><b>NOTE:</b> The Combo box has a lot of listeners on
	 * it, please be "careful" when using it as you may cause unplanned-for
	 * things to happen.</font>
	 * 
	 * @return Combo widget
	 */
	public Combo getCombo() {
		checkWidget();
		return mCombo;
	}

	public FlatCalendarCombo getCCombo() {
		checkWidget();
		return mFlatCombo;
	}

	private void updateDate() {
		if (mStartDate == null) {
			clear();
			return;
		}

		String toSet = DateHelper.getDate(mStartDate, DateHelper.dateFormatFix(mSettings.getDateFormat()));
		setText(toSet);
	}

	/**
	 * Removes date and clears combo, same as setDate(null).
	 */
	public void clear() {
		if (isFlat) {
			mFlatCombo.removeAll();
			mFlatCombo.setText(mSettings.getNoDateSetText());
		} else {
			mCombo.removeAll();
			mCombo.setText(mSettings.getNoDateSetText());
		}

		mStartDate = null;
		mEndDate = null;
	}

	/**
	 * Puts focus on the combo box.
	 * 
	 * @deprecated please use {@link #setFocus()}
	 */
	public void grabFocus() {
		checkWidget();
		if (isFlat)
			mFlatCombo.setFocus();
		else
			mCombo.setFocus();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.swt.widgets.Composite#setFocus()
	 */
	public boolean setFocus() {
		checkWidget();
		if (this.isFlat) {
			return this.getCCombo().getTextControl().setFocus();
		} else {
			return this.mCombo.setFocus();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.swt.widgets.Control#forceFocus()
	 */
	public boolean forceFocus() {
		checkWidget();
		if (this.isFlat) {
			return this.getCCombo().getTextControl().forceFocus();
		} else {
			return this.mCombo.forceFocus();
		}
	}

	public void setEnabled(boolean enabled) {
		checkWidget();
		if (isFlat)
			mFlatCombo.setEnabled(enabled);
		else
			mCombo.setEnabled(enabled);
	}

	public boolean isEnabled() {
		checkWidget();
		return isFlat ? mFlatCombo.getEnabled() : mCombo.getEnabled();
	}

	/**
	 * Adds a modification listener to the combo box.
	 * 
	 * @param ml
	 *            ModifyListener
	 */
	public void addModifyListener(ModifyListener ml) {
		checkWidget();
		if (isFlat) {
			mFlatCombo.addModifyListener(ml);
		} else {
			mCombo.addModifyListener(ml);
		}
	}

	/**
	 * Removes a modification listener from the combo box.
	 * 
	 * @param ml
	 *            ModifyListener
	 */
	public void removeModifyListener(ModifyListener ml) {
		checkWidget();
		if (isFlat) {
			mFlatCombo.removeModifyListener(ml);
		} else {
			mCombo.removeModifyListener(ml);
		}
	}

	/**
	 * The date prior to which selection is not allowed.
	 * 
	 * @return Date
	 */
	public Calendar getDisallowBeforeDate() {
		return mDisallowBeforeDate;
	}

	/**
	 * Sets the date prior to which selection is not allowed.
	 * 
	 * @param disallowBeforeDate
	 *            Date
	 */
	public void setDisallowBeforeDate(Calendar disallowBeforeDate) {
		mDisallowBeforeDate = disallowBeforeDate;
	}

	/**
	 * Sets the date prior to which selection is not allowed.
	 * 
	 * @param disallowBeforeDate
	 *            Date
	 */
	public void setDisallowBeforeDate(Date disallowBeforeDate) {
		Calendar cal = Calendar.getInstance(mSettings.getLocale());
		cal.setTime(disallowBeforeDate);
		mDisallowBeforeDate = cal;
	}

	/**
	 * The date after which selection is not allowed.
	 * 
	 * @return Date
	 */
	public Calendar getDisallowAfterDate() {
		return mDisallowAfterDate;
	}

	/**
	 * Sets the date after which selection is not allowed.
	 * 
	 * @param disallowAfterDate
	 *            Date
	 */
	public void setDisallowAfterDate(Calendar disallowAfterDate) {
		mDisallowAfterDate = disallowAfterDate;
	}

	/**
	 * Sets the date after which selection is not allowed.
	 * 
	 * @param disallowAfterDate
	 */
	public void setDisallowAfterDate(Date disallowAfterDate) {
		Calendar cal = Calendar.getInstance(mSettings.getLocale());
		cal.setTime(disallowAfterDate);
		mDisallowAfterDate = cal;
	}

	/**
	 * Sets the CalendarCombo that will be the recipient of (end date) date
	 * range changes as well as date start date that will be used.
	 * 
	 * @param combo
	 *            CalendarCombo
	 */
	public void setDependingCombo(CalendarCombo combo) {
		mDependingCombo = combo;
	}

	/**
	 * Returns the CalendarCombo that is the recipient of (end date) date range
	 * changes as well as date start date that will be used.
	 * 
	 * @return CalendarCombo
	 */
	public CalendarCombo getDependingCombo() {
		return mDependingCombo;
	}

	public void addControlListener(ControlListener listener) {
		mComboControl.addControlListener(listener);
	}

	public void addDragDetectListener(DragDetectListener listener) {
		mComboControl.addDragDetectListener(listener);
	}

	public void addFocusListener(FocusListener listener) {
		mComboControl.addFocusListener(listener);
	}

	public void addHelpListener(HelpListener listener) {
		mComboControl.addHelpListener(listener);
	}

	public void addKeyListener(KeyListener listener) {
		mComboControl.addKeyListener(listener);
	}

	public void addMenuDetectListener(MenuDetectListener listener) {
		mComboControl.addMenuDetectListener(listener);
	}

	public void addMouseListener(MouseListener listener) {
		mComboControl.addMouseListener(listener);
	}

	public void addMouseMoveListener(MouseMoveListener listener) {
		mComboControl.addMouseMoveListener(listener);
	}

	public void addMouseTrackListener(MouseTrackListener listener) {
		mComboControl.addMouseTrackListener(listener);
	}

	public void addMouseWheelListener(MouseWheelListener listener) {
		mComboControl.addMouseWheelListener(listener);
	}

	public void addPaintListener(PaintListener listener) {
		mComboControl.addPaintListener(listener);
	}

	public void addTraverseListener(TraverseListener listener) {
		mComboControl.addTraverseListener(listener);
	}

	public void addDisposeListener(DisposeListener listener) {
		mComboControl.addDisposeListener(listener);
	}

	public void addListener(int eventType, Listener listener) {
		mComboControl.addListener(eventType, listener);
	}

	public void removeControlListener(ControlListener listener) {
		mComboControl.removeControlListener(listener);
	}

	public void removeDragDetectListener(DragDetectListener listener) {
		mComboControl.removeDragDetectListener(listener);
	}

	public void removeFocusListener(FocusListener listener) {
		mComboControl.removeFocusListener(listener);
	}

	public void removeHelpListener(HelpListener listener) {
		mComboControl.removeHelpListener(listener);
	}

	public void removeKeyListener(KeyListener listener) {
		mComboControl.removeKeyListener(listener);
	}

	public void removeMenuDetectListener(MenuDetectListener listener) {
		mComboControl.removeMenuDetectListener(listener);
	}

	public void removeMouseListener(MouseListener listener) {
		mComboControl.removeMouseListener(listener);
	}

	public void removeMouseMoveListener(MouseMoveListener listener) {
		mComboControl.removeMouseMoveListener(listener);
	}

	public void removeMouseTrackListener(MouseTrackListener listener) {
		mComboControl.removeMouseTrackListener(listener);
	}

	public void removeMouseWheelListener(MouseWheelListener listener) {
		mComboControl.removeMouseWheelListener(listener);
	}

	public void removePaintListener(PaintListener listener) {
		mComboControl.removePaintListener(listener);
	}

	public void removeTraverseListener(TraverseListener listener) {
		mComboControl.removeTraverseListener(listener);
	}

	public void notifyListeners(int eventType, Event event) {
		mComboControl.notifyListeners(eventType, event);
	}

	public void removeDisposeListener(DisposeListener listener) {
		mComboControl.removeDisposeListener(listener);
	}

	public void removeListener(int eventType, Listener listener) {
		mComboControl.removeListener(eventType, listener);
	}

	public Composite getActiveComboControl() {
		return mComboControl;
	}
}
